package com.blyat.xsoft.gateway.security.refresher;

import com.blyat.xsoft.gateway.context.ReactiveXSoftContextHolder;
import com.blyat.xsoft.kernel.component.security.oauth2.OauthConstant;
import com.blyat.xsoft.kernel.component.security.oauth2.util.TokenUtil;
import com.blyat.xsoft.kernel.util.HttpUtil;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @Auther: syh
 * @Date: 2020/9/11
 * @Description:
 */
public class JwtReactiveTokenRefresher extends AbstractReactiveTokenRefresher {
    @Override
    public Mono<String> doRefresh() {
        return ReactiveXSoftContextHolder.get(ServerWebExchange.class).flatMap(exchange -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            Map<String, Object> parameters = new LinkedHashMap<>();
            // OauthJwtAccessTokenConverter中存入access_token中的数据，在这里使用
            parameters.put("client_id", TokenUtil.decryptInfo(getAccessToken(), OauthConstant.OAUTH_CLIENT_ID));
            parameters.put("client_secret", TokenUtil.decryptInfo(getAccessToken(), OauthConstant.OAUTH_CLIENT_SECRET));
            parameters.put("refresh_token", TokenUtil.decryptInfo(getAccessToken(), OauthConstant.OAUTH_REFRESH_TOKEN));
            parameters.put("grant_type", "refresh_token");
            // 发送刷新的http请求
            Map<String, Object> result = null;
            try {
                result = HttpUtil.post(getOauthTokenUrl(request), parameters);
                if (Objects.isNull(result) || result.size() <= 0 || !result.containsKey("access_token")) {
                    return Mono.error(new BadCredentialsException("token invalidate."));
                }

                String accessToken = result.get("access_token").toString();
                OAuth2AccessToken oAuth2AccessToken = getTokenStore().readAccessToken(accessToken);
                OAuth2Authentication auth2Authentication = getTokenStore().readAuthentication(oAuth2AccessToken);
                // 保存授权信息，以便全局调用
                SecurityContextHolder.getContext().setAuthentication(auth2Authentication);

                // 前端收到该event事件时，更新access_token
                HttpHeaders headers = response.getHeaders();
                headers.put("event", Lists.newArrayList("token-refreshed"));
                headers.put("access_token", Lists.newArrayList(accessToken));
                return Mono.just(accessToken);
            } catch (IOException e) {
                e.printStackTrace();
                return Mono.error(new RuntimeException("error to refresh access token"));
            }
        });
    }

    @Override
    public boolean shouldRefresh() {
        // 旧token过期才刷新
        return getAccessToken() != null && getAccessToken().isExpired();
    }

    private String getOauthTokenUrl(ServerHttpRequest request) {
        return String.format("%s://%s:%s%s%s",
                request.getURI().getScheme(),
                request.getURI().getHost(),
                request.getURI().getPort(),
                !Strings.isNullOrEmpty(request.getPath().contextPath().value()) ? "/" + request.getPath().contextPath().value() : "",
                "/oauth/token");
    }
}
